package com.newegg.proxy;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.security.Security;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

public class SocketProxyServer implements Runnable {
	private Socket sourceSocket;

	public void run() {
		String remoteAddress = this.sourceSocket.getRemoteSocketAddress().toString();
		log("process socket:" + remoteAddress);
		InputStream sourceIn = null;
		InputStream proxyIn = null;
		OutputStream sourceOut = null;
		OutputStream proxyOut = null;
		Socket proxySocket = null;
		try {
			sourceIn = this.sourceSocket.getInputStream();
			sourceOut = this.sourceSocket.getOutputStream();
			byte[] tmp = new byte[1];
			int n = sourceIn.read(tmp);
			if (n == 1) {
				int protocol = tmp[0];
				if (4 == protocol) {
					proxySocket = this.convertToSocket4(sourceIn, sourceOut);
				} else if (5 == protocol) {
					proxySocket = this.convertToSocket5(sourceIn, sourceOut);
				} else {
					log("Socket协议错误,不是Socket4或者Socket5");
				}
				if (proxySocket != null) {
					CountDownLatch countDownLatch = new CountDownLatch(1);
					proxyIn = proxySocket.getInputStream();
					proxyOut = proxySocket.getOutputStream();
					this.transfer(sourceIn, proxyOut, countDownLatch);
					this.transfer(proxyIn, sourceOut, countDownLatch);
					try {
						countDownLatch.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			} else {
				log("SOCKET ERROR: " + tmp.toString());
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			this.closeIO(sourceIn);
			this.closeIO(proxyIn);
			this.closeIO(proxyOut);
			this.closeIO(proxyIn);
			this.closeIO(proxySocket);
			this.closeIO(this.sourceSocket);
		}
	}

	public SocketProxyServer(Socket sourceSocket) {
		this.sourceSocket = sourceSocket;
	}

	private static final void log(String message) {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(dateFormat.format(new Date()) + " - " + message);
	}

	private static void startServer(int port) throws IOException{
		log("config =>> port=" + port);
		ServerSocket serverSocket = new ServerSocket(port);
		try {
			Socket socket = null;
			while ((socket = serverSocket.accept()) != null) {
				(new Thread(new SocketProxyServer(socket))).start();
			}
		} finally {
			if (serverSocket != null) {
				serverSocket.close();
			}
		}
	}

	private Socket convertToSocket4(InputStream inputStream, OutputStream outputStream) throws IOException {
		Socket proxySocket = null;
		byte[] tmp = new byte[3];
		inputStream.read(tmp);
		int port = ByteBuffer.wrap(tmp, 1, 2).asShortBuffer().get() & '￿';
		String host = this.getHost((byte) 1, inputStream);
		inputStream.read();
		byte[] response = new byte[8];
		try {
			proxySocket = new Socket(host, port);
			response[1] = 90;
			log("connect " + tmp[1] + "host: " + host + " ,port: " + port);
		} catch (Exception var9) {
			response[1] = 91;
			log("connect error,host: " + host + " ,port: " + port);
		}
		outputStream.write(response);
		outputStream.flush();
		return proxySocket;
	}

	private Socket convertToSocket5(InputStream inputStream, OutputStream outputStream) throws IOException {
		byte[] tmp = new byte[2];
		inputStream.read(tmp);
		byte method = tmp[1];
		if (2 == tmp[0]) {
			method = 0;
			inputStream.read();
		}
		tmp = new byte[]{5, method};
		outputStream.write(tmp);
		outputStream.flush();
		tmp = new byte[4];
		inputStream.read(tmp);
		log("proxy header is:" + Arrays.toString(tmp));
		byte cmd = tmp[1];
		String host = this.getHost(tmp[3], inputStream);
		tmp = new byte[2];
		inputStream.read(tmp);
		int port = ByteBuffer.wrap(tmp).asShortBuffer().get() & '￿';
		log("connect host: " + host + " :port:" + port);
		ByteBuffer rsv = ByteBuffer.allocate(10);
		rsv.put((byte) 5);
		Object resultTmp = null;
		try {
			if (1 == cmd) {
				resultTmp = new Socket(host, port);
				rsv.put((byte) 0);
			} else if (2 == cmd) {
				resultTmp = new ServerSocket(port);
				rsv.put((byte) 0);
			} else {
				rsv.put((byte) 5);
				resultTmp = null;
			}
		} catch (Exception var19) {
			rsv.put((byte) 5);
			resultTmp = null;
		}
		rsv.put((byte) 0);
		rsv.put((byte) 1);
		rsv.put(this.sourceSocket.getLocalAddress().getAddress());
		Short localPort = (short) (this.sourceSocket.getLocalPort() & '￿');
		rsv.putShort(localPort);
		tmp = rsv.array();
		outputStream.write(tmp);
		outputStream.flush();
		if (resultTmp != null && 2 == cmd) {
			ServerSocket ss = (ServerSocket) resultTmp;
			try { resultTmp = ss.accept(); } catch (Exception e) { } finally { this.closeIO(ss); }
		}
		return (Socket) resultTmp;
	}

	private void transfer(InputStream in, OutputStream out, CountDownLatch latch) {
		(new Thread(() -> {
			byte[] bytes = new byte[1024];
			int count;
			try {
				while ((count = in.read(bytes)) > 0) {
					out.write(bytes, 0, count);
					out.flush();
				}
			} catch (IOException e) {
				log("转换出现错误");
				e.printStackTrace();
			}
			if (latch != null) { latch.countDown(); }
		})).start();
	}

	private void closeIO(Closeable closeable) {
		if (closeable != null) {
			try { closeable.close(); } catch (IOException e) { e.printStackTrace(); }
		}
	}

	private String getHost(byte type, InputStream inputStream) throws IOException {
		String host = null;
		byte[] tmp = null;
		switch (type) {
			case 1 :
				tmp = new byte[4];
				inputStream.read(tmp);
				host = InetAddress.getByAddress(tmp).getHostAddress();
				break;
			case 2 :
				tmp = new byte[16];
				inputStream.read(tmp);
				host = InetAddress.getByAddress(tmp).getHostAddress();
				break;
			case 3 :
				int count = inputStream.read();
				tmp = new byte[count];
				inputStream.read(tmp);
				host = new String(tmp);
		}
		return host;
	}

	public static void main(String[] args) throws IOException {
		Security.setProperty("networkaddress.cache.ttl", "86400");
		startServer(8085);
	}
}